package com.example.core.service.impl;

import com.example.core.convert.FormToModelPropertyMap;
import com.example.core.convert.ModelToFormPropertyMap;
import com.example.core.convert.context.RequestContext;
import com.example.core.convert.form.FormConvertor;
import com.example.core.convert.model.ModelConvertor;
import com.example.core.dao.BaseDao;
import com.example.core.form.BaseForm;
import com.example.core.hql.HQLInfo;
import com.example.core.hql.HQLWrapper;
import com.example.core.hql.IHQLBuilder;
import com.example.core.hql.utils.HQLUtil;
import com.example.core.model.BaseModel;
import com.example.core.service.BaseService;
import com.example.core.service.DispatchService;
import com.example.utils.Page;
import com.example.utils.ReflectionKit;
import com.example.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.JpaSort;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class BaseServiceImpl<D extends BaseDao<T>, T extends BaseModel> implements BaseService<D, T> {

    Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private EntityManager entityManager;

    @Autowired
    private D dao;

    @Resource
    private IHQLBuilder hqlBuilder;

    @Resource
    private DispatchService dispatchService;

    @Override
    public D getBaseDao() {
        return this.dao;
    }

    @Override
    public Class<?> getEntityClass() {
        return ReflectionKit.getSuperClassGenricType(getClass(), 1);
    }

    @Override
    public String getEntity() {
        Class<?> entityClass = this.getEntityClass();
        return StringUtils.toLowerCaseFirstOne(entityClass.getSimpleName());
    }

    @Override
    public T findByPrimaryKey(String fdId) {
        return this.findByPrimaryKey(fdId, Boolean.FALSE);
    }

    @Override
    public T findByPrimaryKey(String fdId, Boolean noLazy) {
        if (noLazy) {
            return this.dao.findById(fdId).get();
        }
        return this.dao.getOne(fdId);
    }

    @Override
    public String add(T model) throws Exception {
        model.recalculateFields();
        this.dao.save(model);
        if (this.dispatchService != null) {
            this.dispatchService.add(model);
        }
        return model.getFdId();
    }

    @Override
    public String add(BaseForm form, RequestContext requestContext) throws Exception {
        BaseModel baseModel = this.convertFormToModel(form, requestContext);
        return this.add((T) baseModel);
    }

    @Override
    public void update(T model) throws Exception {
        this.dao.saveAndFlush(model);
        if (this.dispatchService != null) {
            this.dispatchService.update(model);
        }
    }

    @Override
    public void delete(T model) throws Exception {
        if (this.dispatchService != null) {
            this.dispatchService.delete(model);
        }
        this.dao.delete(model);
    }

    @Override
    public Page findPage(HQLInfo hqlInfo) throws Exception {
        Page page = Page.getEmptyPage();
        int total = 0;
        if (hqlInfo.isGettingCount()) {
            HQLInfo cloneHql = (HQLInfo) hqlInfo.clone();
            cloneHql.setSelectBlock("count(*)");
            Query hbmQuery = this.createHbmQuery(cloneHql);
            total = ((Long) hbmQuery.getSingleResult()).intValue();
        }
        if (total > 0) {
            page = new Page();
            page.setRowsize(hqlInfo.getPageSize());
            page.setPageno(hqlInfo.getPageNum());
            page.setTotalrows(total);
            page.excecute();
            Query query = this.createHbmQuery(hqlInfo);
            query.setFirstResult((hqlInfo.getPageNum() - 1) * hqlInfo.getPageSize());
            query.setMaxResults(hqlInfo.getPageSize());
            page.setList(query.getResultList());
        }
        return page;
    }

    @Override
    public List<T> findList(HQLInfo hqlInfo) throws Exception {
        hqlInfo.setSelectBlock(null);
        Query query = this.createHbmQuery(hqlInfo);
        return query.getResultList();
    }

    @Override
    public Query createHbmQuery(HQLInfo hqlInfo) throws Exception {
        if (StringUtils.isEmpty(hqlInfo.getModelName())) {
            Class<?> entityClass = this.getEntityClass();
            hqlInfo.setModelName(entityClass.getSimpleName());
            hqlInfo.setModelClass(entityClass);
            hqlInfo.setModelTable(StringUtils.toLowerCaseFirstOne(entityClass.getSimpleName()));
        }
        HQLWrapper hqlWrapper = hqlBuilder.buildHQlWrapper(hqlInfo);
        Query query = this.entityManager.createQuery(hqlWrapper.getHql());
        HQLUtil.setParameters(query, hqlWrapper.getParameterList());
        return query;
    }

    @Override
    public BaseModel convertFormToModel(BaseForm form, RequestContext requestContext) throws Exception {
        Object model = null;
        if (!StringUtils.isEmpty(form.getFdId())) {
            model = findByPrimaryKey(form.getFdId(), Boolean.TRUE);
        } else {
            model = form.getModelClass().newInstance();
        }
        BeanUtils.copyProperties(form, model, this.getNullPropertyNames(form));
        FormToModelPropertyMap formToModelPropertyMap = form.getToModelPropertyMap();
        for (String propertyName : formToModelPropertyMap.getPropertyMap().keySet()) {
            Field field = ReflectionUtils.findField(form.getClass(), propertyName);
            field.setAccessible(true);
            Object propertyVal = field.get(form);
            Object convert = formToModelPropertyMap.get(propertyName);
            if (convert instanceof FormConvertor) {
                ((FormConvertor) convert).convertor(model, propertyVal);
            }
        }
        if (this.dispatchService != null) {
            this.dispatchService.convertFormToModel(form, requestContext, (BaseModel) model);
        }
        return (BaseModel) model;
    }

    @Override
    public BaseForm convertModelToForm(BaseModel model, RequestContext requestContext) throws Exception {
        ModelToFormPropertyMap modelToFormPropertyMap = model.getToFormPropertyMap();
        Object form = model.getFormClass().newInstance();
        BeanUtils.copyProperties(model, form, this.getNullPropertyNames(model));
        if (!modelToFormPropertyMap.getPropertyMap().isEmpty()) {
            Map<String, Object> propertyMap = modelToFormPropertyMap.getPropertyMap();
            for (String propertyName : propertyMap.keySet()) {
                Object val = ReflectionKit.getCascadePropertyValue(model, propertyName);
                Object convert = propertyMap.get(propertyName);
                if (convert instanceof ModelConvertor) {
                    ((ModelConvertor) convert).convertor(form, val);
                }
            }
        }
        if (this.dispatchService != null) {
            this.dispatchService.cloneModelToForm((BaseForm) form, requestContext, (BaseModel) model);
        }
        return (BaseForm) form;
    }

    @Override
    public BaseForm initFormSetting(RequestContext requestContext) throws Exception {
        Object model = this.getEntityClass().newInstance();
        Class<?> formClass = ((BaseModel<?>) model).getFormClass();
        return (BaseForm) formClass.newInstance();
    }


    private String[] getNullPropertyNames(Object source) {
        BeanWrapper srcWrapper = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = srcWrapper.getPropertyDescriptors();
        Set<String> emptyNames = new HashSet<>();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = srcWrapper.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }
        return emptyNames.toArray(new String[emptyNames.size()]);
    }

}
